#include "stdafx.h"
#include <math.h>
#include "VideoDriver.h"
#include "Shader.h"
#include "Utils.h"

cVideoDriver* cVideoDriver::ms_Instance = NULL;

cVideoDriver::cVideoDriver():m_pFBOShader(NULL), m_pFBOBlackAndWhiteShader(NULL), m_pFBOBlurShader(NULL),
			m_pFBOPreBloomShader(NULL), m_pFBOBloomShader(NULL), m_pFBODOFShader(NULL)
{
	m_FBOProgram = 0;
	for (int i = 0; i < FBO_MAX_INDEX; i++)
	{
		m_aFBOColor[i] = 0;
		m_aFBODepth[i] = 0;
	}
	aVertices[0].x = -1.0f; aVertices[1].x = -1.0f; aVertices[2].x =  1.0f; aVertices[3].x = 1.0f;
	aVertices[0].y = -1.0f; aVertices[1].y =  1.0f; aVertices[2].y = -1.0f; aVertices[3].y = 1.0f;

	m_typeEffect = PPE_FBO;
}

cVideoDriver::~cVideoDriver()
{
	for (int fboIndex = 0; fboIndex < FBO_MAX_INDEX; fboIndex++)
	{
		glDeleteTextures(1, &m_aFBOColor[fboIndex]);
		glDeleteTextures(1, &m_aFBODepth[fboIndex]);
	}
	glDeleteFramebuffers(1, &m_FBOProgram);
}

cVideoDriver* cVideoDriver::GetInstance()
{
	if (ms_Instance == NULL)
	{
		ms_Instance = new cVideoDriver();
	}
	return ms_Instance;
}

void cVideoDriver::DestroyInstance()
{
	SAFE_DEL(ms_Instance);
}

void cVideoDriver::Init()
{
	if(m_FBOProgram == 0)
	{
		glGenFramebuffers(1, &m_FBOProgram);
	}
	else
	{
		LOGE("Re-init FBO buffer\n");
		return;
	}

	for (int fboIndex = 0; fboIndex < FBO_MAX_INDEX; fboIndex++)
	{
		//Color texture
		glGenTextures(1, &m_aFBOColor[fboIndex]);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[fboIndex]);

		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, SCREEN_W, SCREEN_H, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);

		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

		//Depth texture
		glGenTextures(1, &m_aFBODepth[fboIndex]);
		glBindTexture(GL_TEXTURE_2D, m_aFBODepth[fboIndex]);
		
		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SCREEN_W, SCREEN_H, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);

		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

		//Attach
		glBindFramebuffer(GL_FRAMEBUFFER, m_FBOProgram);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_aFBOColor[fboIndex], 0);
		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_aFBODepth[fboIndex], 0);

		int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		if(status != GL_FRAMEBUFFER_COMPLETE)
		{
			 LOGE("Could not create FBO buffer\n");
		}

		//De-attach
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
	}

	m_pFBOShader->Init();
	m_pFBOBlackAndWhiteShader->Init();
	m_pFBOBlurShader->Init();
	m_pFBOPreBloomShader->Init();
	m_pFBOBloomShader->Init();
	m_pFBODOFShader->Init();
}

void cVideoDriver::SetFBO(int fboIndex)
{
	glBindFramebuffer(GL_FRAMEBUFFER, m_FBOProgram);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_aFBOColor[fboIndex], 0);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_aFBODepth[fboIndex], 0);

	glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
}

void cVideoDriver::StopFBO()
{
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void cVideoDriver::DrawFBO(int fboIndex, int colorIndex)
{
	if (fboIndex >= 0)
		SetFBO(fboIndex);

	GLuint program = m_pFBOShader->m_program;
	int location = -1;
	int texIndex = 0;

	glUseProgram(program);

	location = glGetAttribLocation(program, "a_positionL");
	if(location != -1)
	{
		glEnableVertexAttribArray(location);
		glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), &aVertices[0].x);
	}

	location = glGetUniformLocation(program, "u_samplerColor");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[colorIndex]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	if (fboIndex >= 0)
		StopFBO();
}

void cVideoDriver::DrawBlackAndWhite(int fboIndex, int colorIndex)
{
	if (fboIndex >= 0)
		SetFBO(fboIndex);

	GLuint program = m_pFBOBlackAndWhiteShader->m_program;
	int location = -1;
	int texIndex = 0;

	glUseProgram(program);

	location = glGetAttribLocation(program, "a_positionL");
	if(location != -1)
	{
		glEnableVertexAttribArray(location);
		glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), &aVertices[0].x);
	}

	location = glGetUniformLocation(program, "u_samplerColor");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[colorIndex]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	if (fboIndex >= 0)
		StopFBO();
}

void cVideoDriver::DrawBlur(int fboIndex, int colorIndex, float k)
{
	if (fboIndex >= 0)
		SetFBO(fboIndex);

	GLuint program = m_pFBOBlurShader->m_program;
	int location = -1;
	int texIndex = 0;

	glUseProgram(program);

	location = glGetAttribLocation(program, "a_positionL");
	if(location != -1)
	{
		glEnableVertexAttribArray(location);
		glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), &aVertices[0].x);
	}

	location = glGetUniformLocation(program, "u_samplerColor");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[colorIndex]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	location = glGetUniformLocation(program, "u_step");
	if (location != -1)
	{
		float x = 1.0f / SCREEN_W;
		float y = 1.0f / SCREEN_H;
		float z = sqrt(2.0f) / 2.0f * x;
		float w = sqrt(2.0f) / 2.0f * y;
		glUniform4f(location, k * x, k * y, k * z, k * w);
	}

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	if (fboIndex >= 0)
		StopFBO();
}

void cVideoDriver::DrawPreBloom(int fboIndex, int colorIndex, float limit)
{
	if (fboIndex >= 0)
		SetFBO(fboIndex);

	GLuint program = m_pFBOPreBloomShader->m_program;
	int location = -1;
	int texIndex = 0;

	glUseProgram(program);

	location = glGetAttribLocation(program, "a_positionL");
	if(location != -1)
	{
		glEnableVertexAttribArray(location);
		glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), &aVertices[0].x);
	}

	location = glGetUniformLocation(program, "u_samplerColor");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[colorIndex]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	location = glGetUniformLocation(program, "u_limit");
	if (location != -1)
	{
		glUniform1f(location, limit);
	}

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	if (fboIndex >= 0)
		StopFBO();
}

void cVideoDriver::DrawBloom(int fboIndex, int colorIndex, int colorIndex2)
{
	if (fboIndex >= 0)
		SetFBO(fboIndex);

	GLuint program = m_pFBOBloomShader->m_program;
	int location = -1;
	int texIndex = 0;

	glUseProgram(program);

	location = glGetAttribLocation(program, "a_positionL");
	if(location != -1)
	{
		glEnableVertexAttribArray(location);
		glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), &aVertices[0].x);
	}

	location = glGetUniformLocation(program, "u_samplerColor");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[colorIndex]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	location = glGetUniformLocation(program, "u_samplerColor2");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[colorIndex2]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	if (fboIndex >= 0)
		StopFBO();
}

void cVideoDriver::DrawDepthOfField(int fboIndex, int colorIndex, int colorIndex2, int depthIndex, float saturation)
{
	if (fboIndex >= 0)
		SetFBO(fboIndex);

	GLuint program = m_pFBODOFShader->m_program;
	int location = -1;
	int texIndex = 0;

	glUseProgram(program);

	location = glGetAttribLocation(program, "a_positionL");
	if(location != -1)
	{
		glEnableVertexAttribArray(location);
		glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, sizeof(Vector2), &aVertices[0].x);
	}

	location = glGetUniformLocation(program, "u_samplerColor");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[colorIndex]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	location = glGetUniformLocation(program, "u_samplerColor2");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBOColor[colorIndex2]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	location = glGetUniformLocation(program, "u_samplerDepth");
	if (location != -1)
	{
		glActiveTexture(GL_TEXTURE0 + texIndex);
		glBindTexture(GL_TEXTURE_2D, m_aFBODepth[depthIndex]);
		glUniform1i(location, texIndex);
		texIndex++;
	}

	location = glGetUniformLocation(program, "u_saturation");
	if (location != -1)
	{
		glUniform1f(location, saturation);
	}

	glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

	if (fboIndex >= 0)
		StopFBO();
}

void cVideoDriver::DrawPostProcessingEffects(int fboStartIndex)
{
	switch (m_typeEffect)
	{
	case PPE_FBO:
		cVideoDriver::GetInstance()->DrawFBO(-1, fboStartIndex);
		break;

	case PPE_BLACK_AND_WHITE:
		cVideoDriver::GetInstance()->DrawBlackAndWhite(-1, fboStartIndex);
		break;

	case PPE_BLUR:
		{
			int tmpIndex = fboStartIndex + 1;
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex, fboStartIndex, 5);
			cVideoDriver::GetInstance()->DrawBlur(fboStartIndex, tmpIndex, 5);
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex, fboStartIndex, 5);
			cVideoDriver::GetInstance()->DrawBlur(fboStartIndex, tmpIndex, 5);
			cVideoDriver::GetInstance()->DrawBlur(-1, fboStartIndex, 5);
		}
		break;

	case PPE_BLOOM:
		{
			int tmpIndex = fboStartIndex + 1;
			int tmpIndex2 = fboStartIndex + 2;
			cVideoDriver::GetInstance()->DrawPreBloom(tmpIndex, fboStartIndex, 0.7f);
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex2, tmpIndex, 5);
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex, tmpIndex2, 5);
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex2, tmpIndex, 5);
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex, tmpIndex2, 5);
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex2, tmpIndex, 5);
			cVideoDriver::GetInstance()->DrawBloom(-1, fboStartIndex, tmpIndex2);
		}
		break;

	case PPE_DEPTH_OFF_FIELD:
		{
			int tmpIndex = fboStartIndex + 1;
			int tmpIndex2 = fboStartIndex + 2;
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex, fboStartIndex, 5);
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex2, tmpIndex, 9);
			cVideoDriver::GetInstance()->DrawBlur(tmpIndex, tmpIndex2, 16);
			cVideoDriver::GetInstance()->DrawDepthOfField(-1, fboStartIndex, tmpIndex, fboStartIndex, 0.0001f);
		}
		break;

	default:
		break;
	}
}
